﻿using System;
using System.Collections.Generic;
using System.Text;

namespace dnsServerCSharp
{
    public class Server
    {
        private void beginUdpReceive(IAsyncResult ar)
        {
            _beginReceive:
            try
            {
                u.BeginReceive(beginUdpReceive, u);
            }
            catch (System.Exception)
            {
                goto _beginReceive;
            }

            var ep = new System.Net.IPEndPoint(0, 0);
            try
            {
                var data = u.EndReceive(ar, ref ep);
                DnsMessage request = DnsMessage.parse(data);

                Console.WriteLine("request from {0}", ep.Address.ToString());

                DnsMessage response = request.Clone();
                response.header.isResponse = true;

                foreach (DnsQuestion q in request.questions)
                {
                    Console.WriteLine("Question : name {0}, type {1}", q.name, q.recordType);
                    switch(q.recordType)
                    {
                        case DnsRecordType.A:
                            onAQuestion(request, response, q);
                            break;
                        case DnsRecordType.PTR:
                            onPtrQuestion(response, q);
                            break;
                        case DnsRecordType.AAAA:
                            onAAAAQuestion(request, response, q);
                            break;
                    }
                }
                byte[] dataToSent = response.toData();
                u.Send(dataToSent, dataToSent.Length, ep);
            }
            catch (System.Net.Sockets.SocketException /*socketException*/)
            {
                ;
            }
            catch (System.IO.EndOfStreamException /*eofException*/)
            {
                ;
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("Exception : {0}", ex);
            }
        }
        
        private void onAAAAQuestion(DnsMessage request, DnsMessage response, DnsQuestion q)
        {
            if (filter.has(q.name))
            {
                response.header.authoritative = true;

                DnsAAAAData aaaa = new DnsAAAAData();
                aaaa.address = System.Net.IPAddress.Parse("0:0:0:0:0:ffff:101:101");

                DnsResource answer = new DnsResource();
                answer.setData(q.name, DnsRecordType.AAAA, DnsClass.Internet, 0, aaaa.toData());

                response.answers.Add(answer);
            }
            else
            {
                redirect(request, response);
            }
        }
        private void onAQuestion(DnsMessage request, DnsMessage response, DnsQuestion q)
        {
            if (filter.has(q.name))
            {
                response.header.authoritative = true;

                DnsAData a = new DnsAData();
                a.address = System.Net.IPAddress.Parse("1.1.1.1");

                DnsResource answer = new DnsResource();
                answer.setData(q.name, DnsRecordType.A, DnsClass.Internet, 0, a.toData());

                response.answers.Add(answer);
            }
            else if (aRecordTable.has(q.name))
            {
                response.header.authoritative = true;

                DnsAData a = aRecordTable.at(q.name);

                DnsResource answer = new DnsResource();
                answer.setData(q.name, DnsRecordType.A, DnsClass.Internet, 0, a.toData());

                response.answers.Add(answer);
            }
            else
            {
                redirect(request, response);
            }
        }
        private void onPtrQuestion(DnsMessage response, DnsQuestion q)
        {
            if (q.name.ToLower() == "1.0.0.127.in-addr.arpa")
            {
                var ptrData = new DnsPtrData();
                ptrData.name = localServerDomain;

                DnsResource answer = new DnsResource();
                answer.setData(q.name, DnsRecordType.PTR, q.dnsClass, 0, ptrData.toData());
                response.answers.Add(answer);
            }
            else
            {
            }
        }
        private void redirect(DnsMessage request, DnsMessage response)
        {
            var serverResponse = DnsMessage.queryDnsByUdp(request, this.redirectServerName);
            foreach (DnsResource r in serverResponse.answers)
            {
                response.answers.Add(r);
            }
            foreach (DnsResource r in serverResponse.nameServers)
            {
                response.nameServers.Add(r);
            }
            foreach (DnsResource r in serverResponse.additionals)
            {
                response.additionals.Add(r);
            }
            response.header.recursionAvailable = serverResponse.header.recursionAvailable;
        }
        public bool start()
        {
            try
            {
                u = new System.Net.Sockets.UdpClient(this.listeningPort);
                u.BeginReceive(beginUdpReceive, u);
                return true;
            }
            catch (System.Net.Sockets.SocketException socketException)
            {
                Console.WriteLine("Exception : {0}", socketException.Message);
                
            }
            catch (System.IO.EndOfStreamException eocException)
            {
                Console.WriteLine("Exception : {0}", eocException.Message);
            }
            catch (System.Exception ex)
            {
            	Console.WriteLine("Exception : {0}", ex);
            }
            return false;
        }
        public Filter filter = new Filter();
        public ARecordTable aRecordTable = new ARecordTable();
        public string redirectServerName = "202.103.224.68";
        public int listeningPort = 53;

        string localServerDomain = "mydns.com";

        System.Net.Sockets.UdpClient u;
    }
}
